//
//  MSBandFile.m
//  AVPlayer
//
//  Created by Michael Ge on 2018/12/29.
//  Copyright © 2018 Michael. All rights reserved.
//

#import "MSBandFile.h"
#import <UIKit/UIKit.h>
#import <sys/xattr.h>
#import <AVFoundation/AVFoundation.h>

@implementation MSBandFile
+(NSString*)createBand:(NSString *)name audio:(NSString*)audioFilePath
{
    AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:audioFilePath]];
    if (!asset || asset.duration.timescale != 44100){
        return nil;
    }
    
    CMTime duration = asset.duration;
    if (duration.value > 44100 * 30){
        duration.value = 44100 * 30;
    }
    return [self createBand:name audio:audioFilePath start:CMTimeMake(0, 44100) duration:asset.duration];
}


/**
 在一个缓冲区中搜索 8 字节数据，如果找到则返回 8 字节数据的起始地址，如果未找到则返回 NULL

 @param buffer 缓冲区地址
 @param bufferEnd 缓冲区结束地址
 @param eightBytesData 8 字节数据
 @return 如果找到则返回 8 字节数据的起始地址，如果未找到则返回 NULL
 */
char* findEightBytesData(const char* buffer,const char* bufferEnd,const char* eightBytesData)
{
    if (!buffer){
        return NULL;
    }
    
    while ((buffer + 8) <= bufferEnd){
        if (*buffer == eightBytesData[0]){
            if (*((int32_t*)buffer) == *((int32_t*)eightBytesData) &&
                *((int32_t*)(buffer + 4)) == *((int32_t*)(eightBytesData + 4))){
                return (char*)buffer;
            }
        }
        buffer++;
    }
    
    return NULL;
}


/**
 在 document 目录下创建 .band
 
 @param name band 文件名称 (不扩展名 .band)
 @param audioFilePath 音频文件名称，必须是 wav 或 aiff 文件并且采样率为 44100Hz。
 @param start 音频起始位置
 @param duration 音频长度。timescale 必须为 44100，且总长度必需 < 30 秒。
 @return band 文件路径
 */
+(NSString*)createBand:(NSString *)name
                 audio:(NSString*)audioFilePath
                 start:(CMTime)start
              duration:(CMTime)duration
{
    //1. 获取应用目录下的 document 目录
    NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentPath = [pathArray objectAtIndex:0];
    NSString *bandPath = [documentPath stringByAppendingPathComponent:[name stringByAppendingString:@".band"]];
    
    
    //2. 在 document 目录下创建 .band。 e.g.:  document/ringtone.band
    int r = [self ensureDirectory:bandPath];
    if (r == 0){
        //目录创建失败
        return nil;
    }
    //跳过 iCloud 的备份
    [self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:bandPath]];
    
    
    //3. 创建 Contents 子目录
    NSString *contentsDirectory = [bandPath stringByAppendingPathComponent:@"Contents"];
    [self ensureDirectory:contentsDirectory];
    //复制 PagInfo 文件到 Contents 子目录
    [self copyFile:[[NSBundle mainBundle] pathForResource:@"PkgInfo" ofType:@"" inDirectory:@"Res"]
         toDesPath:[contentsDirectory stringByAppendingPathComponent:@"PkgInfo"]
       isOverWrite:YES];
    
    
    //4. 创建 Media 子目录
    NSString *mediaDirectory = [bandPath stringByAppendingPathComponent:@"Media"];
    [self ensureDirectory:mediaDirectory];
    //复制音频文件到 Media 子目录
    if ([audioFilePath hasSuffix:@".wav"] || [audioFilePath hasSuffix:@".wave"]){
        [self copyFile:audioFilePath
             toDesPath:[mediaDirectory stringByAppendingPathComponent:@"ring1.wav"]
           isOverWrite:YES];
    }
    else{
        [self copyFile:audioFilePath
             toDesPath:[mediaDirectory stringByAppendingPathComponent:@"ring.aiff"]
           isOverWrite:YES];
    }

    
    //5.创建 Output 子目录
    NSString *outputDirectory = [bandPath stringByAppendingPathComponent:@"Output"];
    [self ensureDirectory:outputDirectory];
    //复制文件到 Output 子目录
    [self copyFile:[[NSBundle mainBundle] pathForResource:@"_cacheInfo" ofType:@"" inDirectory:@"Res"]
         toDesPath:[outputDirectory stringByAppendingPathComponent:@"_cacheInfo"]
       isOverWrite:YES];
    [self copyFile:[[NSBundle mainBundle] pathForResource:@"arrange_screenshot_big" ofType:@"png" inDirectory:@"Res"]
         toDesPath:[outputDirectory stringByAppendingPathComponent:@"arrange_screenshot_big.png"]
       isOverWrite:YES];
    [self copyFile:[[NSBundle mainBundle] pathForResource:@"arrange_screenshot" ofType:@"png" inDirectory:@"Res"]
         toDesPath:[outputDirectory stringByAppendingPathComponent:@"arrange_screenshot.png"]
       isOverWrite:YES];
    NSString *metadataPath = [outputDirectory stringByAppendingPathComponent:@"metadata.plist"];
    [self copyFile:[[NSBundle mainBundle] pathForResource:@"metadata" ofType:@"plist" inDirectory:@"Res"]
         toDesPath:metadataPath
       isOverWrite:YES];
    //向 metadata.plist 写入音频长度
    NSMutableDictionary *metadata = [NSMutableDictionary dictionaryWithContentsOfFile:metadataPath];
    [metadata setObject:@(duration.value /(float)duration.timescale) forKey:@"com_apple_garageband_metadata_songDuration"];
    [metadata writeToFile:metadataPath atomically:NO];
    
    
    //6. 复制 projectData
    NSString* projectDataPath = [bandPath stringByAppendingPathComponent:@"projectData"];
    [self copyFile:[[NSBundle mainBundle] pathForResource:@"projectData" ofType:@"" inDirectory:@"Res"]
         toDesPath:projectDataPath
       isOverWrite:YES];
    
    
    //修改 projectData
    NSData *base64Data;
    if ([audioFilePath hasSuffix:@".wav"] || [audioFilePath hasSuffix:@".wave"]){
        base64Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"base64data_wav" ofType:@"" inDirectory:@"Res"]];
    }
    else{
        base64Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"base64data_aiff" ofType:@"" inDirectory:@"Res"]];
    }
    
    NSMutableData *binData = [[NSMutableData alloc] initWithBase64EncodedData:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters];
    
    //写入音频长度
    char* bufferStart = (char*)binData.bytes;
    char* buffer = bufferStart;
    char* bufferEnd = buffer + binData.length;

    static const char tag1[] = {0x45,0x56,0x41,0x57,0x00,0x00,0x00,0x00};
    buffer = findEightBytesData(buffer,bufferEnd,tag1);
    if (buffer){
        int64_t v = 0;
        //length
        [self writeUInt64WithLittleEndian:(char*)&v value:(uint64_t)duration.value + start.value];
        [binData replaceBytesInRange:NSMakeRange((buffer - bufferStart) + 12, sizeof(v)) withBytes:(void*)&v];
    }
    
    static const char tag2[] = {0x26,0x88,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF};
    buffer = findEightBytesData(buffer + 8,bufferEnd,tag2);
    if (buffer){
        int64_t v = 0;
        //start
        [self writeUInt64WithLittleEndian:(char*)&v value:(uint64_t)start.value];
        [binData replaceBytesInRange:NSMakeRange((buffer - bufferStart) - 8, sizeof(v)) withBytes:(void*)&v];
        
        //duration
        [self writeUInt64WithLittleEndian:(char*)&v value:(uint64_t)duration.value];
        [binData replaceBytesInRange:NSMakeRange((buffer - bufferStart) + 8, sizeof(v)) withBytes:(void*)&v];
    }
    
    NSString *base64String = [binData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength | NSDataBase64EncodingEndLineWithLineFeed];
    
    NSString *projectData = [NSString stringWithContentsOfFile:projectDataPath encoding:NSUTF8StringEncoding error:nil];
    projectData = [projectData stringByReplacingOccurrencesOfString:@"${base64Data}" withString:base64String];
    
    [self writeFile:projectDataPath data:[projectData dataUsingEncoding:NSUTF8StringEncoding]];
    
    return bandPath;
}


/**
 *  判断 cpu 大小端特性
 *
 *  @return YES 大端 CPU，NO 小端 CPU
 */
+(BOOL)isBigEndianCPU
{
    static const uint32_t number = 0x1;
    return *((char*)(&number)) == 0x0;
}

+(BOOL)is8ByteAlignment:(const void *)address
{
    return !((intptr_t)address & 0x07);
}

+(void)writeUInt32WithLittleEndian:(char*)buffer value:(uint32_t) value
{
    if (![self isBigEndianCPU]){
        *((uint32_t*)buffer) = value;
    }
    else{
        char* src = (char*)&value;
        buffer[0] = src[3];
        buffer[1] = src[2];
        buffer[2] = src[1];
        buffer[3] = src[0];
    }
}

+(void) writeUInt64WithLittleEndian:(char*)buffer value:(uint64_t) value
{
    if (![self isBigEndianCPU]){
        if ([self is8ByteAlignment:buffer]){
            *((uint64_t*)buffer) = value;
        }
        else{
            //ARM CPU 双字访问时必须8字节或4字节对齐，因为不能保证对齐，所以将8字节拆成两个4字节访问。
            *((uint32_t*)buffer)     = *((uint32_t*)&value);
            *((uint32_t*)buffer + 1) = *((uint32_t*)&value + 1);
        }
    }
    else{
        char* src = (char*)&value;
        buffer[0] = src[7];
        buffer[1] = src[6];
        buffer[2] = src[5];
        buffer[3] = src[4];
        buffer[4] = src[3];
        buffer[5] = src[2];
        buffer[6] = src[1];
        buffer[7] = src[0];
    }
}

#pragma mark - 目录操作
+ (NSString * _Nullable)ensureDirectoryInDocument:(NSString * _Nonnull)subPath skipBackup:(BOOL)skip
{
    if (!subPath || subPath.length == 0){
        return nil;
    }
    
    NSArray  *homePaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *hPath = [homePaths objectAtIndex:0];
    NSString *fullPath = [hPath stringByAppendingPathComponent:subPath];
    
    int r = [self ensureDirectory:fullPath];
    if (r != 0) {
        if (skip && r == 2){
            [self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:fullPath]];
        }
        
        return fullPath;
    }
    else{
        return nil;
    }
}

/**
 *  确保某一个目录存在，如果不存在则尝试创建这个目录。
 *  @param path 目录的全路径
 *  @return 如果目录存在 1，如果创建成功返回 2，如果创建失败则返回 0。
 */
+(int)ensureDirectory:(NSString*)path
{
    if (!path) {
        return 0;
    }
    
    NSFileManager *fm = [[NSFileManager alloc] init];
    [fm changeCurrentDirectoryPath:[path stringByExpandingTildeInPath]];
    
    BOOL isDir   = NO;
    BOOL isExist = [fm fileExistsAtPath:path isDirectory:&isDir];
    
    if (!isExist){
        BOOL success = [fm createDirectoryAtPath:path
                     withIntermediateDirectories:YES
                                      attributes:nil
                                           error:nil];
        
        return success ? 2 : 0;
    }
    
    return 1;
}

#pragma mark - 文件操作

/**
 *  复制文件
 *
 *  @param sourcePath   源文件路径
 *  @param desPath      目标文件路径
 *
 *  @return 复制结果
 */
+ (BOOL)copyFile:(NSString *_Nonnull)sourcePath toDesPath:(NSString *_Nonnull)desPath isOverWrite:(BOOL)flag
{
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    NSError *error;
    if ([self isExist:desPath]) {
        if (![self removeFile:desPath]) {
            return NO;
        }
    }
    
    [fileManager copyItemAtPath:sourcePath toPath:desPath error:&error];
    
    if (error) {
        return NO;
    }
    
    return YES;
}


+ (NSString* _Nullable) readFileWithString:(NSString * _Nonnull)fullPath
{
    NSError  *error   = nil;
    NSString *content = nil;
    
    content = [NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:&error];
    if (error) {
        return nil;
    }
    
    return content;
}

/**
 *  删除文件
 *
 *  @param path 文件路径
 *
 *  @return 删除结果
 */
+ (BOOL)removeFile:(NSString * _Nonnull)path
{
    BOOL success = NO;
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    if ([fileManager fileExistsAtPath:path]) {
        [fileManager removeItemAtPath:path error:nil];
        success = true;
    }
    
    return success;
}

/**
 *  写入文件
 *  如果文件不存在则创建并写入数据，如果已存在则覆盖；
 *
 *  @param path 文件路径
 *  @param fildData 数据(NSData)
 *
 *  @return 写入文件结果，写入文件失败或文件目录不存在则返回或目标是一个目录则返回 NO
 */
+ (BOOL)writeFile:(NSString *_Nonnull)path data:(NSData * _Nonnull)fildData
{
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    
    BOOL isDir;
    BOOL isExist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
    if (isExist) {
        if (isDir) {
            return NO;
        }
    }

    return [fileManager createFileAtPath:path contents:fildData attributes:nil];
}

/**
 *  判断一个文件或路径是否存在。
 *
 *  @param fullPath 等检测的路径

 *  @return 是否存在
 */
+ (BOOL)isExist:(NSString *)fullPath
{
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    return [fileManager fileExistsAtPath:fullPath];
}

#pragma mark - iCloud不备份方法
/**
 *  把目录设置为iCloud不自动备份(do not back up)
 *  https://developer.apple.com/library/ios/qa/qa1719/_index.html
 *  支付iOS5.0.1及以上版本的SDK
 *
 *  @param URL 路径
 *  @return 是否设置成功
 */
+ (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    if (!URL) return NO;
    
    NSString *systemVersion = [UIDevice currentDevice].systemVersion;
    
    if ([systemVersion floatValue]>=5.1) {
        NSError *error = nil;
        BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
                                      forKey: NSURLIsExcludedFromBackupKey error: &error];
        if(!success){
            NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
        }
        return success;
    }
    else if ([systemVersion isEqualToString:@"5.0.1"]) {
        
        const char* filePath = [[URL path] fileSystemRepresentation];
        
        const char* attrName = "com.apple.MobileBackup";
        u_int8_t attrValue = 1;
        
        int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
        return result == 0;
    }
    
    return YES;
}
@end
